﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using Damany.Util.Extensions;
using FaceCapturerWrapper;
using ImageProcess;
using OpenCvSharp;

namespace Damany.Imaging.Processors
{
    using Damany.Imaging.Common;

    public class PortraitFinder
    {
        private System.Threading.CancellationToken _cancellationToken;
        public IEnumerable<Damany.Imaging.Common.IFacePostFilter> PostFilters { get; set; }
        public System.Threading.CancellationTokenSource TokenSource { get; set; }
        private System.Drawing.Rectangle _roi;

        public float ImageResizeScale { get; set; }



        public ConfigurationHandlers.FaceSearchConfigSectionHandler Configuration
        {
            set
            {
                if (value.MaxFaceWidth <= value.MinFaceWidth)
                {
                    throw new ArgumentException("MaxFaceWidth must be bigger than  MinFaceWidth");
                }

                var ratio = (double)value.MaxFaceWidth / value.MinFaceWidth;
            }
        }

        private object _roiLocker = new object();
        public System.Drawing.Rectangle ROI
        {
            set
            {
                lock (_roiLocker)
                {
                    _roi = value;
                }
            }
            get
            {
                lock (_roiLocker)
                {
                    return _roi;
                }
            }
        }

        public PortraitFinder()
        {
            this.searcher = new TqyFaceCapturer();
            ImageResizeScale = 0.5f;

            PostFilters = new List<IFacePostFilter>(0);
        }

        public List<Portrait> ProcessFrame(Frame motionFrame, System.Threading.CancellationToken cancellationToken)
        {
            _cancellationToken = cancellationToken;
            var portraits = HandleMotionFrame(motionFrame);
            return portraits;
        }

        private List<Portrait> PostProcessPortraits(List<Portrait> portraits)
        {
            for (var i = 0; i < portraits.Count; i++)
            {
                if (!IsFace(portraits[i]))
                {
                    portraits[i].Dispose();
                    portraits[i] = null;
                }
            }

            return portraits.Where(p => p != null).ToList();
        }

        bool IsFace(Portrait portrait)
        {
            foreach (var facePostFilter in PostFilters)
            {
                if (!facePostFilter.IsFace(portrait))
                {
                    return false;
                }
            }

            return true;
        }



        public List<Portrait> HandleMotionFrame(Frame motionFrame)
        {
            return this.SearchIn(motionFrame);
        }



        private List<Portrait> SearchIn(Frame motionFrame)
        {
            if (ROI.IsEmpty)
            {
                ROI = new Rectangle(0, 0, motionFrame.GetImage().Width, motionFrame.GetImage().Height);
            }

            var partIntrested = motionFrame.GetImage().GetSub(ROI);
            var resizedW = (int)(partIntrested.Width * ImageResizeScale);
            resizedW -= resizedW % 4;
            var resizedH = (int)(partIntrested.Height * ImageResizeScale);
            var resizedImage = new IplImage(resizedW, resizedH, partIntrested.Depth, partIntrested.NChannels);
            partIntrested.Resize(resizedImage, Interpolation.Linear);
            var faces = this.searcher.SearchFaces(resizedImage.ToBitmap(PixelFormat.Format24bppRgb));
            partIntrested.Dispose();
            resizedImage.Dispose();

            if (faces.Length == 0)
            {
                motionFrame.Dispose();
                return new List<Portrait>(0);
            }

            var portraitQuery = from f in faces.Select(ResizeAndTranslate)
                                let faceImg = motionFrame.GetImage().GetSub(f)
                                select new Portrait(faceImg)
                                {
                                    FaceBounds = f,
                                    Frame = motionFrame.Clone(),
                                    CapturedAt = motionFrame.CapturedAt,
                                    DeviceId = motionFrame.DeviceId,
                                };


            return portraitQuery.ToList();

        }

        private Rectangle ResizeAndTranslate(Rectangle arg)
        {
            var result = new Rectangle(
                (int)(arg.X / ImageResizeScale),
                (int)(arg.Y / ImageResizeScale),
                (int)(arg.Width / ImageResizeScale),
                (int)(arg.Height / ImageResizeScale));
            result.Offset(ROI.X, ROI.Y);
            return result;
        }


        private static OpenCvSharp.CvRect FrameToPortrait(OpenCvSharp.CvRect bounds, OpenCvSharp.CvRect faceBounds)
        {
            faceBounds.X -= bounds.X;
            faceBounds.Y -= bounds.Y;

            return faceBounds;
        }

        private static List<Portrait> ExpandPortraitsList(IEnumerable<Frame> motionFrames, IEnumerable<Target> portraits)
        {
            var portraitFoundFrameQuery = from m in motionFrames
                                          join p in portraits
                                            on m.Guid equals p.BaseFrame.guid
                                          select new { Frame = m, Portraits = p, };

            var expanedPortraits = from frame in portraitFoundFrameQuery
                                   from p in frame.Portraits.Portraits
                                   select new Portrait(p.Face)
                                   {
                                       FaceBounds = FrameToPortrait(p.FacesRect, p.FacesRectForCompare),
                                       Frame = frame.Frame.Clone(),
                                       CapturedAt = frame.Frame.CapturedAt,
                                       DeviceId = frame.Frame.DeviceId,
                                   };


            return expanedPortraits.ToList();
        }

        private static IEnumerable<Frame> GetFacelessFrames(IEnumerable<Frame> motionFrames, IEnumerable<Target> portraits)
        {
            var noPortraitFrameQuery = from m in motionFrames
                                       where !portraits.Any(t => t.BaseFrame.guid.Equals(m.Guid))
                                       select m;

            return noPortraitFrameQuery;
        }

        private static IEnumerable<Frame> GetFaceFrames(IEnumerable<Frame> motionFrames, IEnumerable<Target> portraits)
        {
            var portraitFrameQuery = from m in motionFrames
                                     where portraits.Any(t => t.BaseFrame.guid.Equals(m.Guid))
                                     select m;

            return portraitFrameQuery;
        }



        FaceCapturerWrapper.TqyFaceCapturer searcher;
    }
}
